package com.sp.business.normalization.impl;

import java.util.ArrayList;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.sp.generated.xml.firemon.SecurityRule;
import com.sp.generated.xml.firemon.SecurityRuleCollection;

public class DefaultSecurityRuleKeyGenerator extends SecurityRuleKeyGenerator {
private static final Logger logger = LoggerFactory.getLogger(DefaultSecurityRuleKeyGenerator.class);

  public void generateSecurityRuleKeys(SecurityRuleCollection previous, SecurityRuleCollection current)
  {
	//nat rule will not have security policy  
    if (current == null)
      return;

    // key statistics
    int matchedKeyCount = 0;
    int generatedKeyCount = 0;

    long start = System.currentTimeMillis();

    List<SecurityRule> nonMatchedSecurityRules = new ArrayList<SecurityRule>(current.getSecurityRules());
    List<SecurityRule> nonMatchedPreviousSecurityRules;

    if (previous == null)
      nonMatchedPreviousSecurityRules = new ArrayList<SecurityRule>();
    else
      nonMatchedPreviousSecurityRules = new ArrayList<SecurityRule>(previous.getSecurityRules());

    // find exact matches first
    for (SecurityRule securityRule : current.getSecurityRules())
    {
      SecurityRule previousSecurityRule = findSecurityRuleExactMatch(nonMatchedPreviousSecurityRules, securityRule);

      if (previousSecurityRule == null)
        continue;

      copyUID(previousSecurityRule, securityRule);

      // remove the previous securityRule, so it doesn't get matched again
      nonMatchedPreviousSecurityRules.remove(previousSecurityRule);

      // remove the securityRule, so we don't try to match it again
      nonMatchedSecurityRules.remove(securityRule);

      matchedKeyCount++;
    }

    // now try to match remaining, non-exact-matched securityRules
    for (SecurityRule securityRule : nonMatchedSecurityRules)
    {
      SecurityRule previousSecurityRule = findSecurityRuleCloseMatch(nonMatchedPreviousSecurityRules, securityRule);

      // copy or generate the guid
      copyUID(previousSecurityRule, securityRule);

      if (previousSecurityRule == null)
      {
        /* no close match could be found for the securityRule 
         * the securityRule is either a newly-created securityRule, 
         * or it has changed so significantly that 
         * it is effectively a new securityRule.
        */    	  
        generatedKeyCount++;
      }
      else
      {
        // we found a close match,
        // remove the previous securityRule, so it doesn't get matched again
        nonMatchedPreviousSecurityRules.remove(previousSecurityRule);

        matchedKeyCount++;
      }
    }

    logger.info(String.format("Matched %s and generated %s NetScreen securityRule keys in %s ms.", matchedKeyCount,
        generatedKeyCount, System.currentTimeMillis() - start));
  }

  private static SecurityRule findSecurityRuleExactMatch(
      List<SecurityRule> previousSecurityRules,
      SecurityRule securityRule)
  {
    for (SecurityRule previousSecurityRule : previousSecurityRules)
    {
      if (isSecurityRuleExactMatch(previousSecurityRule, securityRule))
        return previousSecurityRule;
    }

    return null;
  }

  private static boolean isSecurityRuleExactMatch(SecurityRule previousSecurityRule, SecurityRule securityRule)
  {
    // test number
    if (!KeyGeneratorBase.isExactMatch(previousSecurityRule.getNumber(), securityRule.getNumber()))
      return false;

    // test name
    if (!KeyGeneratorBase.isExactMatch(previousSecurityRule.getName(), securityRule.getName()))
      return false;

    //TODO: match columns?

    return true;
  }

  private static SecurityRule findSecurityRuleCloseMatch(
      List<SecurityRule> previousSecurityRules,
      SecurityRule securityRule)
  {
    int lowestEditDistance = Integer.MAX_VALUE;
    SecurityRule bestMatch = null;

    for (SecurityRule previousSecurityRule : previousSecurityRules)
    {
      int editDistance = getEditDistance(previousSecurityRule, securityRule);

      if (editDistance < lowestEditDistance)
      {
        lowestEditDistance = editDistance;
        bestMatch = previousSecurityRule;
      }
    }

    // TODO: jcoon - need to make this securityRule matching threshold configurable
    final float maximumAllowableEditDistance = 50;

    if (lowestEditDistance > maximumAllowableEditDistance)
      return null;

    return bestMatch;
  }

  /**
   * Find the edit distance between two SecurityRules.
   * 
   * This is the number of changes needed to change one SecurityRule into another, 
   * where each change is a single modification (single character difference between names, etc).
   * 
   * @param previousSecurityRule
   * @param securityRule
   * @return
   */
  private static int getEditDistance(SecurityRule previousSecurityRule, SecurityRule securityRule)
  {
    // name
    int editDistance = KeyGeneratorBase.getEditDistance(previousSecurityRule.getName(), securityRule.getName());

    // number
    editDistance += KeyGeneratorBase.getEditDistance(previousSecurityRule.getNumber(), securityRule.getNumber());

    return editDistance;
  }
}
